/**
 * 
 */
package stc.skymobi.transport.codec;

import java.util.List;
import java.util.UUID;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import stc.skymobi.bean.bytebean.core.BeanFieldCodec;
import stc.skymobi.bean.tlv.TLVSignal;
import stc.skymobi.bean.tlv.annotation.TLVAttribute;
import stc.skymobi.bean.tlv.encode.TLVEncodeContext;
import stc.skymobi.bean.tlv.encode.TLVEncoderOfBean;
import stc.skymobi.bean.xip.annotation.SsipSignal;
import stc.skymobi.bean.xip.core.XipSignal;
import stc.skymobi.transport.protocol.xip.XipHeader;
import stc.skymobi.util.ByteUtils;


/**
 * @author isdom
 *
 */
public class MinaMixedEncoder implements ProtocolEncoder  {
    
    private static final Logger logger = LoggerFactory.getLogger(MinaMixedEncoder.class);
    private int dumpBytes = 256;
    private	boolean	isDebugEnabled;
    private	BeanFieldCodec xipBeanCodec;
    private	TLVEncoderOfBean tlvBeanEncoder;

    /**
	 * @return the tlvBeanEncoder
	 */
	public TLVEncoderOfBean getTlvBeanEncoder() {
		return tlvBeanEncoder;
	}

	/**
	 * @param tlvBeanEncoder the tlvBeanEncoder to set
	 */
	public void setTlvBeanEncoder(TLVEncoderOfBean tlvBeanEncoder) {
		this.tlvBeanEncoder = tlvBeanEncoder;
	}

	/**
	 * @return the xipBeanCodec
	 */
	public BeanFieldCodec getXipBeanCodec() {
		return xipBeanCodec;
	}

	/**
	 * @param xipBeanCodec the xipBeanCodec to set
	 */
	public void setXipBeanCodec(BeanFieldCodec xipBeanCodec) {
		this.xipBeanCodec = xipBeanCodec;
	}

	private XipHeader createHeader(byte basicVer, UUID id, int messageCode, int messageLen) {

		XipHeader 	header = new XipHeader();

        header.setTransaction(id);
        
        int headerSize = xipBeanCodec.getStaticByteSize(XipHeader.class);
        
        header.setLength(headerSize + messageLen);
        header.setMessageLength(messageLen);
		header.setMessageCode( messageCode );
		header.setBasicVer(basicVer);
		
		return	header;
	}
	
	private byte[] encodeXip( XipSignal signal) throws Exception {
    	//	once
        byte[] bytesBody =
        	xipBeanCodec.encode(
    			xipBeanCodec.getEncContextFactory().createEncContext(signal, signal.getClass(), null));
        
		SsipSignal attr = signal.getClass().getAnnotation(SsipSignal.class);
		if ( null == attr ) {
			throw new RuntimeException("invalid ssip signal, bcs of no messageCode.");
		}
		
        XipHeader 	header = createHeader((byte)1, signal.getIdentification(), 
        		attr.messageCode(), bytesBody.length);
        
    	header.setTypeForClass(signal.getClass());

        byte[] bytes = ArrayUtils.addAll(
        	xipBeanCodec.encode(
    				xipBeanCodec.getEncContextFactory().createEncContext(
    						header, XipHeader.class, null) ),
    		bytesBody );
        
        if ( logger.isDebugEnabled() && isDebugEnabled ) {
            logger.debug( "encode XipMessage:" + signal );
            logger.debug("and XipMessage raw bytes -->");
            logger.debug( ByteUtils.bytesAsHexString(bytes, dumpBytes) );
        }
        
        return	bytes;
	}
	
	private byte[] encodeTLV(TLVSignal signal) throws Exception {
    	
		TLVEncodeContext ctx = 
    		tlvBeanEncoder.getEncodeContextFactory().createEncodeContext(signal.getClass(), null);
        List<byte[]> byteList = tlvBeanEncoder.encode(signal, ctx);
        
    	TLVAttribute attr = signal.getClass().getAnnotation(TLVAttribute.class);
    	if ( null == attr ) {
    		throw new RuntimeException("invalid top tlv object, missing @TLVAttribute.");
    	}
		
        byte[] bytesBody = ByteUtils.union( byteList );
        XipHeader 	header = createHeader((byte)2, signal.getIdentification(), 
        		attr.tag(), bytesBody.length);
        
        header.setSourceId(signal.getSourceId());
    	header.setTypeForClass(signal.getClass());
    	
        byte[] bytes = ArrayUtils.addAll(
        	xipBeanCodec.encode(
    				xipBeanCodec.getEncContextFactory().createEncContext(
    						header, XipHeader.class, null) ),
    		bytesBody );
        
        if ( logger.isDebugEnabled() && isDebugEnabled ) {
            logger.debug( "encode TLV:" + signal );
            logger.debug("and TLV raw bytes -->");
            logger.debug( ByteUtils.bytesAsHexString(bytes, dumpBytes) );
        }
        
        return bytes;
	}
	
	/* (non-Javadoc)
     * @see org.apache.mina.filter.codec.ProtocolEncoder#encode(org.apache.mina.core.session.IoSession, java.lang.Object, org.apache.mina.filter.codec.ProtocolEncoderOutput)
     */
    public void encode(IoSession session, Object message, ProtocolEncoderOutput out)
            throws Exception {

    	byte[] bytes = null;
    	if ( message instanceof XipSignal ) {
    		bytes = encodeXip((XipSignal)message);
    	}
    	else if (message instanceof TLVSignal ) {
    		bytes = encodeTLV((TLVSignal)message);
    	}
    	else {
	        throw   new RuntimeException("encode: bean " + message 
	        		+ " is not neigher XipSignal nor TLVSignal.");
    	}
        if ( null != bytes ) {
        	if ( logger.isDebugEnabled() ) {
        		logger.debug("bean type {} and encode size is {}", message.getClass(), bytes.length);
        	}
            out.write( IoBuffer.wrap(bytes) );
        }
        else {
            logger.error("encode: " + message + " can not generate byte stream.");
        }
    }

	public boolean isDebugEnabled() {
		return isDebugEnabled;
	}

	public void setDebugEnabled(boolean isDebugEnabled) {
		this.isDebugEnabled = isDebugEnabled;
	}

	public int getDumpBytes() {
		return dumpBytes;
	}
	
    /**
     * @param dumpBytes the dumpBytes to set
     */
    public void setDumpBytes(int dumpBytes) {
        this.dumpBytes = dumpBytes;
    }

	public void dispose(IoSession session) throws Exception {
	}
}
